package com.example.origin.demo.exam;

import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.LRUCache;
import lombok.Data;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Function;

public class LocalCacheAndConcurrent {


    @Data
    public class ShopInfo {

        //商户Id
        private Integer id;
        //商户名称
        private String name;
        //商户地址
        private String address;
    }


    public class ShopService {

        //已经提供的方法1：批量根据商户1d查询商户名称
//限制：入参shopIds不大于10个
        public Map<Integer, String> getShopName(List<Integer> shopIds) {
            return Collections.emptyMap();
        }

        //已经提供的方法2：批量根据商户1d查询商户地址
//限制：入参shopIds不大于10个
        public Map<Integer, String> getShopAddress(List<Integer> shopIds) {
            return Collections.emptyMap();
        }
    }

    public class AService {

        private ShopService shopService = new ShopService();

        /*
        目标：根据商户id批量查询商户信息，入参shopIds不大于100个
        要求：
        1,并行调用ShopService提供的getShopName.和getShopAddress.方法获取商户名称和地址，构造ShopInfo
        2,从ShopService:获取数据时，需要满足ShopService"入参shopIds不大于10个"的限制条件，即需要分批调用
        3.实现本地缓存，优先从本地缓存获取数据，如果本地缓存没有数据，再从ShopService获取数据
        米*/

        @Resource
        ThreadPoolExecutor threadPoolExecutor;

        LRUCache<Object, Object> lruCache = CacheUtil.newLRUCache(1000);

        private void partitionGet(List<Integer> shopIds,String cacheKey, Map<Integer, String> shopMap,
                                  Function<List<Integer>, Map<Integer, String>> function) {
            List<Integer> list = new ArrayList<>(10);
            for (int i = 0; i < shopIds.size(); i++) {
                list.add(shopIds.get(i));
                if (list.size() >= 10) {
                    extracted(cacheKey, shopMap, function, list);
                    list.clear();
                }
            }
            extracted(cacheKey, shopMap, function, list);
        }

        private void extracted(String cacheKey, Map<Integer, String> shopMap,
                               Function<List<Integer>, Map<Integer, String>> function, List<Integer> list) {
            Map<Integer, String> tmpMap =new HashMap<>();
            List<Integer> ids = new ArrayList<>();
            for (Integer id : list) {
                Object o = lruCache.get(cacheKey + ":" + id);
                if (o==null){
                    ids.add(id);
                    continue;
                }
                tmpMap.put(id,(String)o);
            }
            if (tmpMap.size()<10) {
                Map<Integer, String> apply = function.apply(ids);
                for (Map.Entry<Integer, String> entry : apply.entrySet()) {
                    tmpMap.put(entry.getKey(),entry.getValue());
                    lruCache.put(cacheKey +":"+entry.getKey(),entry.getValue());
                }
            }
            shopMap.putAll(tmpMap);
        }

        public Map<Integer, ShopInfo> getShopInfo(List<Integer> shopIds) {
            int size = shopIds.size();
            Map<Integer, String> shopAddressMap = new HashMap<>(size);
            Map<Integer, String> shopNameMap = new HashMap<>(size);
            CompletableFuture<Void> shopAddressMapFuture = CompletableFuture.runAsync(()
                    -> {
                partitionGet(shopIds,"shopAddress", shopAddressMap, ids -> shopService.getShopAddress(ids));
            }, threadPoolExecutor);
            CompletableFuture<Void> shopNameMapFuture = CompletableFuture.runAsync(()
                    -> {
                partitionGet(shopIds,"shopName", shopNameMap, ids -> shopService.getShopName(ids));
            }, threadPoolExecutor);
            CompletableFuture.allOf(shopAddressMapFuture, shopNameMapFuture).join();

            Map<Integer, ShopInfo> shopInfoMap = new HashMap<>(size);
            for (Integer shopId : shopIds) {
                ShopInfo shopInfo = new ShopInfo();
                shopInfo.setId(shopId);
                shopInfo.setName(shopNameMap.get(shopId));
                shopInfo.setAddress(shopAddressMap.get(shopId));
                shopInfoMap.put(shopId, shopInfo);
            }
            return shopInfoMap;
        }
    }
}
